package com.appsmith.server.services.ce;

import com.appsmith.server.configurations.CommonConfig;
import com.appsmith.server.constants.FieldName;
import com.appsmith.server.domains.UsagePulse;
import com.appsmith.server.domains.User;
import com.appsmith.server.dtos.UsagePulseDTO;
import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException;
import com.appsmith.server.helpers.ce.bridge.Bridge;
import com.appsmith.server.helpers.ce.bridge.BridgeUpdate;
import com.appsmith.server.repositories.UsagePulseRepository;
import com.appsmith.server.services.ConfigService;
import com.appsmith.server.services.OrganizationService;
import com.appsmith.server.services.SessionUserService;
import com.appsmith.server.services.UserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import reactor.core.publisher.Mono;

import java.time.Instant;

import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;

@RequiredArgsConstructor
@Slf4j
public class UsagePulseServiceCEImpl implements UsagePulseServiceCE {

    private final UsagePulseRepository repository;

    private final SessionUserService sessionUserService;

    private final UserService userService;

    private final OrganizationService organizationService;

    private final ConfigService configService;

    private final CommonConfig commonConfig;

    /**
     * To create a usage pulse
     *
     * @param usagePulseDTO UsagePulseDTO
     * @return Mono of UsagePulse
     */
    @Override
    public Mono<UsagePulse> createPulse(UsagePulseDTO usagePulseDTO) {
        if (null == usagePulseDTO.getViewMode()) {
            return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.VIEW_MODE));
        } else if (FALSE.equals(usagePulseDTO.getViewMode()) && usagePulseDTO.getAnonymousUserId() != null) {
            log.warn(
                    "Ignoring usage pulse: anonymous user attempted edit-mode pulse. viewMode={}, hasAnonymousId={}",
                    usagePulseDTO.getViewMode(),
                    usagePulseDTO.getAnonymousUserId() != null);
            return Mono.empty();
        }

        // TODO remove this condition after multi-tenancy is introduced
        if (TRUE.equals(commonConfig.isCloudHosting())) {
            return Mono.just(new UsagePulse());
        }

        UsagePulse usagePulse = new UsagePulse();
        usagePulse.setEmail(null);
        usagePulse.setViewMode(usagePulseDTO.getViewMode());

        Mono<User> currentUserMono = sessionUserService.getCurrentUser();
        Mono<String> organizationIdMono = organizationService.getCurrentUserOrganizationId();
        Mono<String> instanceIdMono = configService.getInstanceId();

        return Mono.zip(currentUserMono, organizationIdMono, instanceIdMono).flatMap(tuple -> {
            User user = tuple.getT1();
            String organizationId = tuple.getT2();
            String instanceId = tuple.getT3();
            usagePulse.setOrganizationId(organizationId);
            usagePulse.setInstanceId(instanceId);

            if (user.isAnonymous()) {
                if (StringUtils.isBlank(usagePulseDTO.getAnonymousUserId())) {
                    log.warn("Ignoring usage pulse: missing anonymous user id for anonymous user.");
                    return Mono.empty();
                }
                usagePulse.setIsAnonymousUser(true);
                usagePulse.setUser(usagePulseDTO.getAnonymousUserId());
                return save(usagePulse);
            }
            usagePulse.setIsAnonymousUser(false);
            BridgeUpdate updateUserObj = Bridge.update();

            String hashedEmail = user.getHashedEmail();
            if (StringUtils.isEmpty(hashedEmail)) {
                hashedEmail = DigestUtils.sha256Hex(user.getEmail());
                // Hashed user email is stored to user for future mapping of user and pulses
                updateUserObj.set(User.Fields.hashedEmail, hashedEmail);
            }
            usagePulse.setUser(hashedEmail);

            updateUserObj.set(User.Fields.lastActiveAt, Instant.now());

            return userService
                    .updateWithoutPermission(user.getId(), updateUserObj)
                    .then(save(usagePulse));
        });
    }

    /**
     * To save usagePulse to the database
     *
     * @param usagePulse UsagePulse
     * @return Mono of UsagePulse
     */
    public Mono<UsagePulse> save(UsagePulse usagePulse) {
        return repository.save(usagePulse);
    }
}
